/*
             MyUSB Library
     Copyright (C) Dean Camera, 2008.
              
  dean [at] fourwalledcubicle [dot] com
      www.fourwalledcubicle.com

 Released under the LGPL Licence, Version 3
*/

/** \file
 *
 *  Configuration descriptor parser API. This section of the library gives a friendly API which can be used in
 *  host applications to easily parse an attached device's configuration descriptor so that endpoint, interface
 *  and other descriptor data can be extracted and used as needed.
 */

#ifndef __CONFIG_DESCRIPTOR_H__
#define __CONFIG_DESCRIPTOR_H__

	/* Includes: */
		#include <avr/io.h>
		
		#include "../../../Common/Common.h"
		#include "../LowLevel/HostChapter9.h"
		#include "../HighLevel/StdDescriptors.h"
		
	/* Enable C linkage for C++ Compilers: */
		#if defined(__cplusplus)
			extern "C" {
		#endif

	/* Public Interface - May be used in end-application: */
		/* Macros: */
			/** Casts a pointer to a descriptor inside the configuration descriptor into a pointer to the given
			 *  descriptor type.
			 *
			 *  Usage Example:
			 *  \code
			 *  uint8_t* CurrDescriptor = &ConfigDescriptor[0]; // Pointing to the configuration header
			 *  USB_Descriptor_Configuration_Header_t* ConfigHeaderPtr = DESCRIPTOR_PCAST(CurrDescriptor,
			 *                                                           USB_Descriptor_Configuration_Header_t);
			 *
			 *  // Can now access elements of the configuration header struct using the -> indirection operator
			 *  \endcode
			 */
			#define DESCRIPTOR_PCAST(DescriptorPtr, Type) ((Type*)DescriptorPtr)

			/** Casts a pointer to a descriptor inside the configuration descriptor into the given descriptor
			 *  type (as an actual struct instance rather than a pointer to a struct).
			 *
			 *  Usage Example:
			 *  \code
			 *  uint8_t* CurrDescriptor = &ConfigDescriptor[0]; // Pointing to the configuration header
			 *  USB_Descriptor_Configuration_Header_t ConfigHeader = DESCRIPTOR_CAST(CurrDescriptor,
			 *                                                       USB_Descriptor_Configuration_Header_t);
			 *
			 *  // Can now access elements of the configuration header struct using the . operator
			 *  \endcode
			 */
			#define DESCRIPTOR_CAST(DescriptorPtr, Type)  (*DESCRIPTOR_PCAST(DescriptorPtr, Type))

			/** Returns the descriptor's type, expressed as the 8-bit type value in the header of the descriptor.
			 *  This value's meaning depends on the descriptor's placement in the descriptor, but standard type
			 *  values can be accessed in the DescriptorTypes_t enum located in USB/HighLevel/StdDescriptors.h.
			 */
			#define DESCRIPTOR_TYPE(DescriptorPtr)        DESCRIPTOR_CAST(DescriptorPtr, USB_Descriptor_Header_t).Type

			/** Returns the descriptor's size, expressed as the 8-bit value indicating the number of bytes. */
			#define DESCRIPTOR_SIZE(DescriptorPtr)        DESCRIPTOR_CAST(DescriptorPtr, USB_Descriptor_Header_t).Size

			/** Creates a prototype or begins a descriptor comparitor routine. Descriptor comparitor routines are 
			 *  small search routines which are passed a pointer to the current sub descriptor in the configuration
			 *  descriptor, and which analyse the sub descriptor to determine whether or not it matches the routine's
			 *  search parameters. Comparitor routines provide a powerful way to scan through the config descriptor
			 *  for certain descriptors matching unique criteria.
			 *
			 *  Comparitor routines are passed in a single pointer named CurrentDescriptor, and should return a value
			 *  of a member of the DSEARCH_Return_ErrorCodes_t enum.
			 */
			#define DESCRIPTOR_COMPARATOR(name)           uint8_t DCOMP_##name (void* CurrentDescriptor)

			/** Searches for the next descriptor in the given configuration descriptor using a premade comparator
			 *  function. The routine updates the position and remaining configuration descriptor bytes values
			 *  automatically.
			 *
			 *  \param DSize    Pointer to an int storing the remaining bytes in the configuration descriptor
			 *  \param DPos     Pointer to the current position in the configuration descriptor
			 *  \param DSearch  Name of the comparitor search function to use on the configuration descriptor
			 *
			 *  \return Value of one of the members of the DSEARCH_Comp_Return_ErrorCodes_t enum
			 *
			 *  Usage Example:
			 *  \code
			 *  DESCRIPTOR_COMPARATOR(EndpointSearcher); // Comparator Prototype
			 *
			 *  DESCRIPTOR_COMPARATOR(EndpointSearcher)
			 *  {
			 *     if (DESCRIPTOR_TYPE(CurrentDescriptor) == DTYPE_Endpoint)
			 *         return Descriptor_Search_Found;
			 *     else
			 *         return Descriptor_Search_NotFound;
			 *  }
			 *
			 *  //...
			 *  // After retrieving configuration descriptor:
			 *  if (USB_Host_GetNextDescriptorComp(&BytesRemaining, &ConfigDescriptorData, EndpointSearcher) ==
			 *      Descriptor_Search_Comp_Found)
			 *  {
			 *      // Do something with the endpoint descriptor
			 *  }
			 *  \endcode
			 */
			#define USB_Host_GetNextDescriptorComp(DSize, DPos, DSearch) \
			                                              USB_Host_GetNextDescriptorComp_P(DSize, DPos, DCOMP_##DSearch)
		/* Enums: */
			/** Enum for return values of a descriptor comparator made with DESCRIPTOR_COMPARATOR. */
			enum DSEARCH_Return_ErrorCodes_t
			{
				Descriptor_Search_Found                = 0, /**< Current descriptor matches comparator criteria. */
				Descriptor_Search_Fail                 = 1, /**< No further descriptor could possibly match criteria, fail the search. */
				Descriptor_Search_NotFound             = 2, /**< Current descriptor does not match comparator criteria. */
			};

			/** Enum for return values of USB_Host_GetNextDescriptorComp() */
			enum DSEARCH_Comp_Return_ErrorCodes_t
			{
				Descriptor_Search_Comp_Found           = 0, /**< Configuration descriptor now points to decriptor which matches
				                                             *   search criteria of the given comparator function. */
				Descriptor_Search_Comp_Fail            = 1, /**< Comparator function returned Descriptor_Search_Fail. */
				Descriptor_Search_Comp_EndOfDescriptor = 2, /**< End of configuration descriptor reached before match found. */
			};
	
		/* Function Prototypes: */
			/** Retrieves the configuration descriptor data or size from an attached device via a standard request.
			 *
			 *  \param ConfigSizePtr  Pointer to a uint16_t for either storing or retrieving the configuration
			 *         descriptor size
			 *
			 *  \param BufferPtr  Pointer to the buffer for storing the configuration descriptor data. If this is
			 *                    NULL, the size of the configuration descriptor will be retrieved instead and
			 *                    placed in the variable pointed to by ConfigSizePtr. If this is non-NULL, the number
			 *                    of bytes indicated by ConfigSizePtr of the configuration descriptor will be loaded
			 *                    into the buffer
			 */
			uint8_t USB_Host_GetDeviceConfigDescriptor(uint16_t* const ConfigSizePtr, void* BufferPtr)
			                                           ATTR_NON_NULL_PTR_ARG(1);

		/* Inline Functions: */
			/** Skips over the current sub-descriptor inside the configuration descriptor, so that the pointer then
			    points to the next sub-descriptor. The bytes remaining value is automatically decremented.
			 *
			 * \param BytesRem  Pointer to the number of bytes remaining of the configuration descriptor
			 * \param CurrConfigLoc  Pointer to the current descriptor inside the configuration descriptor
			 */
			static inline void USB_Host_GetNextDescriptor(uint16_t* const BytesRem,
			                                              uint8_t** const CurrConfigLoc) 
														  ATTR_NON_NULL_PTR_ARG(1, 2);									  
			static inline void USB_Host_GetNextDescriptor(uint16_t* const BytesRem,
			                                              uint8_t** const CurrConfigLoc)
			{
				uint16_t CurrDescriptorSize = DESCRIPTOR_CAST(*CurrConfigLoc, USB_Descriptor_Header_t).Size;

				*CurrConfigLoc += CurrDescriptorSize;
				*BytesRem      -= CurrDescriptorSize;
			}

			/** Skips to the next sub-descriptor inside the configuration descriptor of the specified type value.
			 *  The bytes remaining value is automatically decremented.
			 *
			 * \param BytesRem  Pointer to the number of bytes remaining of the configuration descriptor
			 * \param CurrConfigLoc  Pointer to the current descriptor inside the configuration descriptor
			 * \param Type  Descriptor type value to search for
			 */
			static inline void USB_Host_GetNextDescriptorOfType(uint16_t* const BytesRem,
			                                                    uint8_t** const CurrConfigLoc,
			                                                    const uint8_t Type)
			                                                    ATTR_NON_NULL_PTR_ARG(1, 2);
			static inline void USB_Host_GetNextDescriptorOfType(uint16_t* const BytesRem,
			                                                    uint8_t** const CurrConfigLoc,
			                                                    const uint8_t Type)
			{
				while (*BytesRem)
				{
					USB_Host_GetNextDescriptor(BytesRem, CurrConfigLoc);	  

					if (DESCRIPTOR_TYPE(*CurrConfigLoc) == Type)
					  return;
				}
			}

			/** Skips to the next sub-descriptor inside the configuration descriptor of the specified type value,
			 *  which must come before a descriptor of the second given type value. If the BeforeType type
			 *  descriptor is reached first, the number of bytes remaining to process is set to zero and the
			 *  function exits. The bytes remaining value is automatically decremented.
			 *
			 * \param BytesRem  Pointer to the number of bytes remaining of the configuration descriptor
			 * \param CurrConfigLoc  Pointer to the current descriptor inside the configuration descriptor
			 * \param Type  Descriptor type value to search for
			 * \param BeforeType  Descriptor type value which must not be reached before the given Type descriptor
			 */
			static inline void USB_Host_GetNextDescriptorOfTypeBefore(uint16_t* const BytesRem,
			                                                          uint8_t** const CurrConfigLoc,
			                                                          const uint8_t Type,
			                                                          const uint8_t BeforeType)
			                                                          ATTR_NON_NULL_PTR_ARG(1, 2);
			static inline void USB_Host_GetNextDescriptorOfTypeBefore(uint16_t* const BytesRem,
			                                                          uint8_t** const CurrConfigLoc,
			                                                          const uint8_t Type,
			                                                          const uint8_t BeforeType)
			{
				while (*BytesRem)
				{
					USB_Host_GetNextDescriptor(BytesRem, CurrConfigLoc);

					if (DESCRIPTOR_TYPE(*CurrConfigLoc) == Type)
					{
						return;
					}
					else if (DESCRIPTOR_TYPE(*CurrConfigLoc) == BeforeType)
					{
						*BytesRem = 0;
						return;
					}
				}
			}

			/** Skips to the next sub-descriptor inside the configuration descriptor of the specified type value,
			 *  which must come after a descriptor of the second given type value. The bytes remaining value is
			 *  automatically decremented.
			 *
			 * \param BytesRem  Pointer to the number of bytes remaining of the configuration descriptor
			 * \param CurrConfigLoc  Pointer to the current descriptor inside the configuration descriptor
			 * \param Type  Descriptor type value to search for
			 * \param AfterType  Descriptor type value which must be reached before the given Type descriptor
			 */
			static inline void USB_Host_GetNextDescriptorOfTypeAfter(uint16_t* const BytesRem,
			                                                         uint8_t** const CurrConfigLoc,
			                                                         const uint8_t Type,
																	 const uint8_t AfterType)
			                                                         ATTR_NON_NULL_PTR_ARG(1, 2);
			static inline void USB_Host_GetNextDescriptorOfTypeAfter(uint16_t* const BytesRem,
			                                                         uint8_t** const CurrConfigLoc,
			                                                         const uint8_t Type,
			                                                         const uint8_t AfterType)
			{
				USB_Host_GetNextDescriptorOfType(BytesRem, CurrConfigLoc, AfterType);
				
				if (*BytesRem)
				  USB_Host_GetNextDescriptorOfType(BytesRem, CurrConfigLoc, Type);
			}
			
	/* Private Interface - For use in library only: */
	#if !defined(__DOXYGEN__)
		/* Function Prototypes: */
			uint8_t USB_Host_GetNextDescriptorComp_P(uint16_t* BytesRem, uint8_t** CurrConfigLoc,
                                                     uint8_t (*SearchRoutine)(void*));
	#endif
			
	/* Disable C linkage for C++ Compilers: */
		#if defined(__cplusplus)
			}
		#endif
		
#endif
